package pkg.andru.service;

import java.util.LinkedList;
import java.util.List;

import pkg.andru.service.DownloadEvent.ErrorStatus;
import pkg.andru.service.DownloadEvent.Status;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.IBinder;
import android.os.Process;
import android.util.Log;

public class DownloadService extends Service {

    public static final String EXTRA_DOWNLOAD_EVENT = "nds:extra_download_event";
    public static final String EXTRA_REMOVE_DOWNLOAD_EVENT = "nds:extra_remove_download_event";

    private static int MAX_RETRY_TIMES = 1;

    static final String TAG = "DownloadService";
    private Downloader mDownloader;

    /**
     * set the max retry times, if the retry times is bigger than this, this
     * event will be removed, and the default value is 1
     * 
     * @param times
     */
    public static final void setMaxRetryTimes(int times) {
        if (times >= 0) {
            MAX_RETRY_TIMES = times;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind");
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate");
        mDownloader = new Downloader(this);
        mDownloader.startDownload();
    }

    /**
     * check the download is finished or not
     * 
     * @return
     */
    public boolean isDownloadFinished() {
        boolean result = true;
        if (mDownloader != null) {
            result = mDownloader.isDownloadQueueEmpty();
        }
        return result;
    }

    /**
     * refresh download
     */
    public void refreshDownload() {
        if (mDownloader != null) {
            mDownloader.makeThreadNotify();
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand");
        if (intent != null) {
            List<DownloadEvent> events = intent.getParcelableArrayListExtra(EXTRA_DOWNLOAD_EVENT);
            boolean remove = intent.getBooleanExtra(EXTRA_REMOVE_DOWNLOAD_EVENT, false);
            if (events != null) {
                mDownloader.modifyDownloadQueue(events, remove);
            }
        }
        return START_REDELIVER_INTENT;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mDownloader.stopDownload();
    }

    class Downloader extends Thread implements StateMonitorCallback {

        static final int NETWORK_ERROR_THREAD_WAIT_TIME = 45 * 1000;

        LinkedList<DownloadEvent> mDownloadQueue = new LinkedList<DownloadEvent>();

        private DownloadEvent mCurrentEvent;
        private int mEventId;
        private StateMonitor mMonitor;
        private DownloadEventWatcher mWatcher;

        volatile boolean isRunning;

        int errorTimes;
        int retryTimes;

        public Downloader(Context context) {
            mMonitor = new StateMonitor(context);
            mMonitor.setMonitorCallback(this);
            mWatcher = DownloadEventWatcher.getInstance();
        }

        public void startDownload() {
            if (!isRunning) {
                isRunning = true;
                start();
                Log.i(TAG, "Downloader startDownload");
                mMonitor.startMonitor();
            }
        }

        public void stopDownload() {
            isRunning = false;
            makeThreadNotify();
            mMonitor.stopMonitor();
            mWatcher.unRegisterAllDownloadEventListeners();
            Log.i(TAG, "Downloader stopDownload");
        }

        public boolean isDownloadQueueEmpty() {
            synchronized (mDownloadQueue) {
                return mDownloadQueue.isEmpty();
            }
        }

        public void modifyDownloadQueue(List<DownloadEvent> events, boolean remove) {
            if (isRunning && events != null) {
                synchronized (mDownloadQueue) {
                    for (DownloadEvent event : events) {
                        if (remove) {
                            if (mDownloadQueue.contains(event)) {
                                mDownloadQueue.remove(event);
                            }
                        } else {
                            if (!mDownloadQueue.contains(event)) {
                                mDownloadQueue.add(event);
                            }
                        }
                    }
                    errorTimes = 0;
                    mDownloadQueue.notify();
                }
            }
        }

        @Override
        public void run() {
            setName("nds");
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

            while (isRunning) {
                synchronized (mDownloadQueue) {
                    if (mDownloadQueue.isEmpty()) {
                        try {
                            mDownloadQueue.wait();
                        } catch (InterruptedException e) {
                            Log.w(TAG, "interrupt download save thread");
                        }
                    }
                }
                if (!mDownloadQueue.isEmpty() && isRunning) {
                    mCurrentEvent = mDownloadQueue.getFirst();
                    mEventId = mCurrentEvent.eventId();
                    if (mMonitor.isNetworkRight()) {
                        final DownloadEvent.Status result = mCurrentEvent.onDownload();
                        if (result == Status.STATUS_OK) {
                            removeCurrentEvent();
                            mWatcher.notifyDownloadEventListeners(mEventId, true, null);
                        } else if (result == Status.STATUS_STORAGE_ERROR) {
                            mWatcher.notifyDownloadEventListeners(mEventId, false, ErrorStatus.STORAGE_ERROR);
                            makeThreadWait("STORAGE ERROR");
                        } else if (result == Status.STATUS_RETRY) {
                            retryTimes++;
                            if (retryTimes > MAX_RETRY_TIMES) {
                                moveCurrentEventToTheLast();
                                mWatcher.notifyDownloadEventListeners(mEventId, false, ErrorStatus.MAX_RETRY_ERROR);
                            }
                        } else if (result == Status.STATUS_NETWORK_ERROR) {
                            mWatcher.notifyDownloadEventListeners(mEventId, false, ErrorStatus.NETWORK_ERROR);
                            makeThreadWait("NETWORK ERROR");
                        }
                    } else {
                        mWatcher.notifyDownloadEventListeners(mEventId, false, ErrorStatus.NO_NETWORK);
                        makeThreadWait("NETWORK IS NOT OK");
                    }
                }
            }
        }

        private void makeThreadWait(String reason) {
            synchronized (mDownloadQueue) {
                try {
                    Log.w(TAG, String.format("Thread Wait[reason:%s]", reason));
                    errorTimes = 0;
                    mDownloadQueue.wait();
                } catch (InterruptedException e) {
                    Log.w(TAG, "interrupt download save thread");
                }
            }
        }

        private void makeThreadWait(String reason, int waitTime) {
            synchronized (mDownloadQueue) {
                try {
                    Log.w(TAG, String.format("thread wait[reason:%s,wait time:%d milliseconds]", reason, waitTime));
                    errorTimes = 0;
                    mDownloadQueue.wait(waitTime);
                } catch (InterruptedException e) {
                    Log.w(TAG, "interrupt download save thread");
                }
            }
        }

        private void makeThreadNotify() {
            Log.i(TAG, "notify thread");
            synchronized (mDownloadQueue) {
                errorTimes = 0;
                mDownloadQueue.notify();
            }
        }

        private void moveCurrentEventToTheLast() {
            synchronized (mDownloadQueue) {
                Log.i(TAG, "remove current and add to the last");
                DownloadEvent event = mDownloadQueue.removeFirst();
                mDownloadQueue.addLast(event);
                mCurrentEvent = null;
            }
        }

        private void removeCurrentEvent() {
            synchronized (mDownloadQueue) {
                Log.i(TAG, "remove call from download queue");
                mDownloadQueue.removeFirst();
                retryTimes = 0;
                mCurrentEvent = null;
            }
        }

        @Override
        public void onNetworkRight() {
            makeThreadNotify();
        }

        @Override
        public void onStorageRight() {
            makeThreadNotify();
        }

    }

    public class StateMonitor extends BroadcastReceiver {

        Context mContext;
        ConnectivityManager mNetworkManager;
        StateMonitorCallback mCallback;

        public StateMonitor(Context context) {
            mContext = context;
            mNetworkManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
        }

        public void startMonitor() {
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
            intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
            intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
            mContext.registerReceiver(this, intentFilter);
        }

        public void stopMonitor() {
            mContext.unregisterReceiver(this);
        }

        public void setMonitorCallback(StateMonitorCallback callback) {
            mCallback = callback;
        }

        public boolean isNetworkRight() {
            NetworkInfo info = mNetworkManager.getActiveNetworkInfo();
            return info != null && info.isConnected();
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (mCallback != null) {
                if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) && isNetworkRight()) {
                    mCallback.onNetworkRight();
                } else if (action.equals(Intent.ACTION_DEVICE_STORAGE_LOW)) {

                } else if (action.equals(Intent.ACTION_DEVICE_STORAGE_OK)) {
                    mCallback.onStorageRight();
                }
            }

        }

    }

    static interface StateMonitorCallback {
        public void onNetworkRight();

        public void onStorageRight();
    }

}
